package org.bdware.irp.irplib.core;

import org.bdware.irp.irplib.exception.IrpMessageDecodeException;
import org.bdware.irp.irplib.util.EncoderUtils;
import org.bdware.irp.irplib.util.GlobalUtils;
import org.bdware.irp.irplib.util.IrpCommon;
import org.bdware.irp.irplib.exception.IrpMessageEncodeException;
import org.bdware.irp.irplib.util.GlobalUtils;
import org.bdware.irp.irplib.util.EncoderUtils;

import java.util.Date;
import java.security.Signature;
import java.util.HashMap;
import java.util.Map;

import com.nimbusds.jose.jwk.JWK;
/**
 *
 * Description:
 * <p>
 *  Message implementation
 * </p>
 * Time:2021-07<br>
 *
 * @author:pku<br>
 * @version:$Revision: 1.0 $<br>
 * @since 1.0
 */
public abstract class IrpMessage {
    public byte[] encodedMessageBody;
    public byte[] encodedCredential;
    public byte[] encodedMessage;
    public long createTime; //The message create time

    public IrpMessageEnvelope envelope;
    public IrpMessageHeader header;
    public IrpMessageBody body;
    public IrpMessageCredential credential;

    /**
     * IrpMessage Constructor
     */
    public IrpMessage(int opCode){
        encodedMessage = null;
        encodedMessageBody = null;
        encodedCredential = null;

        envelope = new IrpMessageEnvelope();
        header = new IrpMessageHeader(opCode);
        body = new IrpMessageBody();
        credential = new IrpMessageCredential();
        createTime = System.currentTimeMillis();
    }
    /**
     * IrpMessage Constructor
     */
    public IrpMessage(int opCode, int responseCode){
        encodedMessage = null;
        encodedMessageBody = null;
        encodedCredential = null;

        envelope = new IrpMessageEnvelope();
        header = new IrpMessageHeader(opCode, responseCode);
        body = new IrpMessageBody();
        credential = new IrpMessageCredential();
        createTime = System.currentTimeMillis();
    }

    public IrpMessage(int opCode, int responseCode, int requestId){
        encodedMessage = null;
        encodedMessageBody = null;
        encodedCredential = null;

        envelope = new IrpMessageEnvelope(requestId);
        header = new IrpMessageHeader(opCode, responseCode);
        body = new IrpMessageBody();
        credential = new IrpMessageCredential();
        createTime = System.currentTimeMillis();
    }

    //sign the massage by Signature, need init first
    public final void signMessage(Signature signer) {
        try{
            //generate the message signature data first
            byte[] digestData = getEncodedMessageHeaderBody();
            signer.update(digestData);

            credential.signedInfoType = IrpCommon.CREDENTIAL_SIGNEDINFO_TYPE_RSA;
            credential.signedInfoDigestAlgorithm = GlobalUtils.getDigestAlgFromSignature(signer.getAlgorithm());
            credential.signature = signer.sign();
            encodedMessage = null;  //update the encode message
        }catch(Exception e){
            e.printStackTrace();
        }

    }

    //sign the massage by JWK
    public final void signMessage(JWK jwk) {
        try {
            //generate the message signature data first
            byte[] digestData = getEncodedMessageHeaderBody();
            //not digest the body and header, sign immediately
            String signature = GlobalUtils.signByteArrayByJWK(digestData, jwk);
            credential.signerDoid = GlobalUtils.encodeString(jwk.getKeyID());
            credential.signedInfoType = IrpCommon.CREDENTIAL_SIGNEDINFO_TYPE_JWK;
            credential.signedInfoDigestAlgorithm = IrpCommon.CREDENTIAL_DIGEST_ALG_JWK;
            credential.signature = GlobalUtils.encodeString(signature);
            credential.signedInfoLength = credential.signedInfoDigestAlgorithm.length + credential.signature.length;
            encodedMessage = null;  //update the encode message
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public final String getSignature(){
        if (this.credential.signature != null){
            return GlobalUtils.decodeString(this.credential.signature);
        }else{
            return null;
        }
    }

    //return the encodedMessageBody(if not exists, encode the message header and body first)
    public final byte[] getEncodedMessageHeaderBody() throws IrpMessageEncodeException {
        if (encodedMessageBody != null)
            return encodedMessageBody;
        //encode the message header and body
        encodedMessageBody = EncoderUtils.encodeMessageBody(this);
        return encodedMessageBody;
    }

    //return the encodedCredential(if not exists, encode the message credential first)
    public final byte[] getEncodeMessageCredential() throws IrpMessageEncodeException {
        if (encodedCredential != null)
            return encodedCredential;
        //encode the credential
        if (header.getCertifiedFlag() == true){
            encodedCredential = EncoderUtils.encodeMessageCredential(this.credential);
        }else{
            encodedCredential = new byte[4];
            EncoderUtils.writeIntToBuffer(0, encodedCredential, 0);
        }

        return encodedCredential;
    }

    public final byte[] getEncodedMessage() throws IrpMessageEncodeException {
        if (encodedMessage != null)
            return encodedMessage;
        //encode the message header and body
        getEncodedMessageHeaderBody();
        //encode the credential
        getEncodeMessageCredential();
        //generate the encoded message bytes
        encodedMessage = new byte[encodedMessageBody.length + encodedCredential.length];
        System.arraycopy(encodedMessageBody, 0, encodedMessage, 0, encodedMessageBody.length);
        System.arraycopy(encodedCredential, 0, encodedMessage, encodedMessageBody.length, encodedCredential.length);

        return encodedMessage;
    }

    public static byte[][] fromMap2Byte(Map<String,String> hashMap) {
        byte[][] bytes = new byte[hashMap.size()][];
        //doidValues = new Byte[hashMap.size()][];
        int index = 0;
        for (String key: hashMap.keySet()){
            byte[] keyCode = GlobalUtils.encodeString(key);
            byte[] valueCode = GlobalUtils.encodeString(hashMap.get(key));
            byte[] entryCode = new byte[EncoderUtils.INT_SIZE + keyCode.length + EncoderUtils.INT_SIZE + valueCode.length];
            int offset = EncoderUtils.writeStringByteToBuffer(keyCode, entryCode, 0);
            EncoderUtils.writeStringByteToBuffer(valueCode, entryCode, offset);
            bytes[index] = entryCode;
            index++;
        }

        return bytes;
    }

    public static Map<String,String> fromBytes2Map(byte[][] buf) throws IrpMessageDecodeException {
        Map<String,String> doidValue = new HashMap<>();
        for(byte[] sigBuf : buf){
            byte[] keyCode = EncoderUtils.readStringByteFromBuffer(sigBuf, 0);
            int offsetPos = EncoderUtils.INT_SIZE + keyCode.length;
            byte[] valueCode = EncoderUtils.readStringByteFromBuffer(sigBuf, offsetPos);
            doidValue.put(GlobalUtils.decodeString(keyCode), GlobalUtils.decodeString(valueCode));
        }
        return doidValue;
    }

    @Override
    public String toString() {
        String str = "version=" + ((int) envelope.majorVersion) + '.' + ((int) envelope.minVersion)
            + "; opCode=" + header.opCode
            + "; responseCode=" + header.responseCode
            + "; opFlag" + header.getOpFlagByFlags()
            + "; bodyLength" + header.bodyLength
            + "; createTime:" + new Date(createTime);
        return str;
    }

}
